/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../../toolkit/keyboard.h"
#include "../../toolkit/xml.h"
#include "../misc.h"
#include "../geom.h"
#include "../frame.h"
#include "../actions.h"
#include "../focus_cycle.h"
#include "../window.h"
#include "../stacking.h"
struct options {
	gboolean interactive;
	gboolean desktop_windows;
	enum wm_direction direction;
	gboolean bar;
	gboolean raise;
	GSList *actions;
};
static gboolean cycling = FALSE;

static void end_cycle(gboolean cancel, guint state, struct options *o)
{
	struct wm_client *ft;
	ft = focus_directional_cycle(o->direction, o->desktop_windows, o->interactive, o->bar, TRUE, cancel);
	cycling = FALSE;
	if(ft)
		actions_run_acts(o->actions, WM_USER_ACTION_KEYBOARD_KEY, state, -1, -1, 0, WM_FRAME_CONTEXT_NONE, ft);
	stacking_restore();
}


static gboolean i_input_func(guint initial_state, XEvent *e, struct t_ic *ic, gpointer options, gboolean *used)
{
	guint mods;
	(void)ic;
	(void)used;
	mods = t_keyboard_only_modmasks(e->xkey.state);
	if(e->type == KeyRelease) {
		mods &= ~t_keyboard_keyevent_to_modmask(e);
	}
	if(e->type == KeyPress) {
		KeySym sym = t_keyboard_keypress_to_keysym(e);
		if(sym == XK_Escape) {
			end_cycle(TRUE, e->xkey.state, options);
			return FALSE;
		} else if((sym == XK_Return || sym == XK_KP_Enter) && !initial_state) {
			end_cycle(FALSE, e->xkey.state, options);
			return FALSE;
		}
	} else if(e->type == KeyRelease && initial_state && !(mods & initial_state)) {
		end_cycle(FALSE, e->xkey.state, options);
		return FALSE;
	}
	return TRUE;
}

static void i_cancel_func(gpointer options)
{
	if(cycling)
		end_cycle(TRUE, 0, options);
}

static gpointer setup_func(xmlNodePtr node)
{
	xmlNodePtr n;
	struct options *o;
	o = g_slice_new0(struct options);
	o->bar = TRUE;
	if((n = t_xml_find_node(node, "bar")))
		o->bar = t_xml_node_bool(n);
	if((n = t_xml_find_node(node, "raise")))
		o->raise = t_xml_node_bool(n);
	if((n = t_xml_find_node(node, "desktop")))
		o->desktop_windows = t_xml_node_bool(n);
	if((n = t_xml_find_node(node, "direction"))) {
		gchar *s = t_xml_node_string(n);
		if(!g_ascii_strcasecmp(s, "north") || !g_ascii_strcasecmp(s, "up"))
			o->direction = WM_DIRECTION_NORTH;
		else if(!g_ascii_strcasecmp(s, "northwest"))
			o->direction = WM_DIRECTION_NORTHWEST;
		else if(!g_ascii_strcasecmp(s, "northeast"))
			o->direction = WM_DIRECTION_NORTHEAST;
		else if(!g_ascii_strcasecmp(s, "west") || !g_ascii_strcasecmp(s, "left"))
			o->direction = WM_DIRECTION_WEST;
		else if(!g_ascii_strcasecmp(s, "east") || !g_ascii_strcasecmp(s, "right"))
			o->direction = WM_DIRECTION_EAST;
		else if(!g_ascii_strcasecmp(s, "south") || !g_ascii_strcasecmp(s, "down"))
			o->direction = WM_DIRECTION_SOUTH;
		else if(!g_ascii_strcasecmp(s, "southwest"))
			o->direction = WM_DIRECTION_SOUTHWEST;
		else if(!g_ascii_strcasecmp(s, "southeast"))
			o->direction = WM_DIRECTION_SOUTHEAST;
		g_free(s);
	}
	if((n = t_xml_find_node(node, "finalactions"))) {
		xmlNodePtr m;
		m = t_xml_find_node(n->children, "action");
		while(m) {
			struct wm_actions_act *action = actions_parse(m);
			if(action)
				o->actions = g_slist_append(o->actions, action);
			m = t_xml_find_node(m->next, "action");
		}
	} else {
		o->actions = g_slist_prepend(o->actions, actions_parse_string("Focus"));
		o->actions = g_slist_prepend(o->actions, actions_parse_string("Raise"));
	}
	return o;
}

static gpointer setup_cycle_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_func(node);
	(void)pre;
	(void)post;
	o->interactive = TRUE;
	*input = i_input_func;
	*cancel = i_cancel_func;
	return o;
}

static gpointer setup_target_func(xmlNodePtr node)
{
	struct options *o = setup_func(node);
	o->interactive = FALSE;
	return o;
}

static void free_func(gpointer options)
{
	struct options *o = options;
	while(o->actions) {
		actions_act_unref(o->actions->data);
		o->actions = g_slist_delete_link(o->actions, o->actions);
	}
	g_slice_free(struct options, o);
}

static gboolean run_func(struct wm_actions_data *data, gpointer options)
{
	struct options *o = options;
	if(!o->interactive)
		end_cycle(FALSE, data->state, o);
	else {
		struct wm_client *ft;
		ft = focus_directional_cycle(o->direction, o->desktop_windows, TRUE, o->bar, FALSE, FALSE);
		cycling = TRUE;
		stacking_restore();
		if(o->raise && ft)
			stacking_temp_raise(CLIENT_AS_WINDOW(ft));
	}
	return o->interactive;
}

static gpointer setup_north_cycle_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_cycle_func(node, pre, input, cancel, post);
	o->direction = WM_DIRECTION_NORTH;
	return o;
}

static gpointer setup_south_cycle_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_cycle_func(node, pre, input, cancel, post);
	o->direction = WM_DIRECTION_SOUTH;
	return o;
}

static gpointer setup_east_cycle_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_cycle_func(node, pre, input, cancel, post);
	o->direction = WM_DIRECTION_EAST;
	return o;
}

static gpointer setup_west_cycle_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_cycle_func(node, pre, input, cancel, post);
	o->direction = WM_DIRECTION_WEST;
	return o;
}

static gpointer setup_northwest_cycle_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_cycle_func(node, pre, input, cancel, post);
	o->direction = WM_DIRECTION_NORTHWEST;
	return o;
}

static gpointer setup_northeast_cycle_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_cycle_func(node, pre, input, cancel, post);
	o->direction = WM_DIRECTION_EAST;
	return o;
}

static gpointer setup_southwest_cycle_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_cycle_func(node, pre, input, cancel, post);
	o->direction = WM_DIRECTION_SOUTHWEST;
	return o;
}

static gpointer setup_southeast_cycle_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_cycle_func(node, pre, input, cancel, post);
	o->direction = WM_DIRECTION_SOUTHEAST;
	return o;
}

static gpointer setup_north_target_func(xmlNodePtr node)
{
	struct options *o = setup_target_func(node);
	o->direction = WM_DIRECTION_NORTH;
	return o;
}

static gpointer setup_south_target_func(xmlNodePtr node)
{
	struct options *o = setup_target_func(node);
	o->direction = WM_DIRECTION_SOUTH;
	return o;
}

static gpointer setup_east_target_func(xmlNodePtr node)
{
	struct options *o = setup_target_func(node);
	o->direction = WM_DIRECTION_EAST;
	return o;
}

static gpointer setup_west_target_func(xmlNodePtr node)
{
	struct options *o = setup_target_func(node);
	o->direction = WM_DIRECTION_WEST;
	return o;
}

static gpointer setup_northwest_target_func(xmlNodePtr node)
{
	struct options *o = setup_target_func(node);
	o->direction = WM_DIRECTION_NORTHWEST;
	return o;
}

static gpointer setup_northeast_target_func(xmlNodePtr node)
{
	struct options *o = setup_target_func(node);
	o->direction = WM_DIRECTION_NORTHEAST;
	return o;
}

static gpointer setup_southwest_target_func(xmlNodePtr node)
{
	struct options *o = setup_target_func(node);
	o->direction = WM_DIRECTION_SOUTHWEST;
	return o;
}

static gpointer setup_southeast_target_func(xmlNodePtr node)
{
	struct options *o = setup_target_func(node);
	o->direction = WM_DIRECTION_SOUTHEAST;
	return o;
}

void action_directionalwindows_startup(void)
{
	actions_register_i("DirectionalCycleWindows", setup_cycle_func, free_func, run_func);
	actions_register("DirectionalTargetWindow", setup_target_func, free_func, run_func);
	actions_register_i("DirectionalFocusNorth", setup_north_cycle_func, free_func, run_func);
	actions_register_i("DirectionalFocusSouth", setup_south_cycle_func, free_func, run_func);
	actions_register_i("DirectionalFocusWest", setup_west_cycle_func, free_func, run_func);
	actions_register_i("DirectionalFocusEast", setup_east_cycle_func, free_func, run_func);
	actions_register_i("DirectionalFocusNorthWest", setup_northwest_cycle_func, free_func, run_func);
	actions_register_i("DirectionalFocusNorthEast", setup_northeast_cycle_func, free_func, run_func);
	actions_register_i("DirectionalFocusSouthWest", setup_southwest_cycle_func, free_func, run_func);
	actions_register_i("DirectionalFocusSouthEast", setup_southeast_cycle_func, free_func, run_func);
	actions_register("DirectionalTargetNorth", setup_north_target_func, free_func, run_func);
	actions_register("DirectionalTargetSouth", setup_south_target_func, free_func, run_func);
	actions_register("DirectionalTargetWest", setup_west_target_func, free_func, run_func);
	actions_register("DirectionalTargetEast", setup_east_target_func, free_func, run_func);
	actions_register("DirectionalTargetNorthWest", setup_northwest_target_func, free_func, run_func);
	actions_register("DirectionalTargetNorthEast", setup_northeast_target_func, free_func, run_func);
	actions_register("DirectionalTargetSouthWest", setup_southwest_target_func, free_func, run_func);
	actions_register("DirectionalTargetSouthEast", setup_southeast_target_func, free_func, run_func);
}
